#include "preHeader.h"
#include "MapInstance.h"
#include "VacuousObject.inl"
#include "Session/InstancePacketHandler.h"
#include "Session/PlayerPacketHandler.h"
#include "Session/GateServerMgr.h"
#include "Session/GameServerMgr.h"
#include "Spell/Spell.h"
#include "Script/LuaGame.h"
#include "Object/PlayerMgr.h"

#define INSTANCE_INIT_TASK_NUM (2)

const MapInstance::AoiOrderArgs MapInstance::defAoiOrderArgs{AoiOrder::None, 0};
const MapInstance::TileOrderArgs MapInstance::defTileOrderArgs{};

MapInstance::MapInstance(
	const GameMap& gameMap, InstGUID instGuid, ObjGUID ownerGuid)
: L(NULL)
, sWheelTimerMgr(67, GET_SYS_TIME)
, sTileHandler(gameMap.GetTileDefine())
, m_gameMap(gameMap)
, m_instGuid(instGuid)
, m_ownerGuid(ownerGuid)
, m_isShutdown(false)
, m_istobeShutdown(false)
, m_isPlayGameOver(false)
, m_deleteExpireTime(0)
, m_taskWaitInitCnt(INSTANCE_INIT_TASK_NUM)
, m_pHook(NULL)
, m_fightStage(ArenaFightStage::eUnInit)
, m_tickSurplusMS(0)
, m_gcSurplusMS(0)
, m_MapTeamManager(this)
, m_navPhysicsFlags(0)
, m_creatureSeed(0)
, m_staticObjectSeed(0)
, m_auraObjectSeed(0)
, m_mapHookUniqueKey(0)
{
	sCharUpdateInfoBitMask.Resize((int)CharUpdateInfo::Type::Count);
}

MapInstance::~MapInstance()
{
	if (!IsCleaned()) {
		WLOG("Destruct map instance[%hu,%hu,%u] error.",
			m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID);
	}

	for (auto itr = m_allSpawnObject.begin(); itr != m_allSpawnObject.end();) {
		RemoveSpawnObject(*itr++);
	}

	for (auto itr = m_AutoPlayerStorageMap.begin(); itr != m_AutoPlayerStorageMap.end();) {
		itr++->second->RemoveFromWorld(this);
	}
	for (auto& pair : m_StrayPlayerStorageMap) {
		pair.second->Delete(this);
	}
	for (auto itr = m_PlayerStorageMap.begin(); itr != m_PlayerStorageMap.end();) {
		itr++->second->RemoveFromWorld(this);
	}
	for (auto itr = m_CreatureStorageMap.begin(); itr != m_CreatureStorageMap.end();) {
		itr++->second->RemoveFromWorld(this);
	}
	for (auto itr = m_StaticObjectStorageMap.begin(); itr != m_StaticObjectStorageMap.end();) {
		itr++->second->RemoveFromWorld(this);
	}
	for (auto itr = m_AuraObjectStorageMap.begin(); itr != m_AuraObjectStorageMap.end();) {
		itr++->second->RemoveFromWorld(this);
	}

	ApplyAllAoiTileActorOrders();
	for (auto pObject : m_pendingDeleteObjects) {
		delete pObject;
	}

	DestructAllMapHookInfos();
	SAFE_DELETE(m_pHook);
	lua::close(L);
}

void MapInstance::PushRecvPacket(const Packet& packet)
{
	if (packet.pck->GetOpcode() == CMSG_RPC_INVOKE_RESP) {
		sRPCManager.OnRPCReply(packet.pck);
	} else {
		InstanceService::PushRecvPacket(packet);
	}
}

int MapInstance::HandleInstancePacket(const Packet & packet)
{
	return sInstancePacketHandler.HandlePacket(this, *packet.pck, packet.gsIdx);
}

int MapInstance::HandlePlayerPacket(const Packet & packet)
{
	auto pPlayer = GetPlayer(packet.guid);
	if (pPlayer != NULL) {
		return sPlayerPacketHandler.HandlePacket(pPlayer, *packet.pck);
	}
	WLOG("Handle Player(%hu,%u) Opcode[%u] Object Not Exist!",
		packet.guid.SID, packet.guid.UID, packet.pck->GetOpcode());
	return SessionHandleSuccess;
}

WheelTimerMgr *MapInstance::GetWheelTimerMgr()
{
	return &sWheelTimerMgr;
}

void MapInstance::Init()
{
	sAsyncTaskMgr.AddTask(CreateAsyncTask([=]() {
		L = lua::open();
		InitLuaEnv(L);
		m_pHook->InitLuaEnv();
		LoadAllSpawnObjects();
		DoneOneInitTask();
	}), this, m_instGuid.UID);
}

void MapInstance::Start()
{
	m_pHook->OnMapInstanceStart();
}

void MapInstance::Shutdown()
{
	if (IstobeShutdown()) {
		return;
	}

	CreateTimer([=]() {
		if (IsCleaned()) {
			RemoveTimers(TimerTypeShutdownInstance);
			m_deleteExpireTime = GET_SYS_TIME + 3000;
			m_isShutdown = true;
			return;
		}
		auto itr = m_PlayerStorageMap.begin();
		while (itr != m_PlayerStorageMap.end()) {
			ForcePlayerLeave(itr++->second);
		}
	}, TimerTypeShutdownInstance, 300);
	m_istobeShutdown = true;
}

void MapInstance::PlayGameOver()
{
	m_isPlayGameOver = true;
}

void MapInstance::Update(uint64 diffTime)
{
	UpdateAllPackets();
	UpdateAllSafeEvents();

	sTileHandler.UpdateActive();
	sWheelTimerMgr.Update(GET_SYS_TIME);
	m_pHook->Update(diffTime);

	AsyncTaskOwner::UpdateTask();
	for (auto& pair : m_AutoPlayerStorageMap) {
		pair.second->AsyncTaskOwner::UpdateTask();
	}
	for (auto& pair : m_PlayerStorageMap) {
		pair.second->AsyncTaskOwner::UpdateTask();
	}

	auto itr = m_pendingDeleteObjects.begin();
	while (itr != m_pendingDeleteObjects.end()) {
		auto pLocatableObject = *itr;
		if (pLocatableObject->IsKindOf(TYPE_PLAYER)) {
			((Player*)pLocatableObject)->UpdateTask();
		}
		if (pLocatableObject->IsDeletable()) {
			itr = m_pendingDeleteObjects.erase(itr);
			delete pLocatableObject;
		} else {
			++itr;
		}
	}

	if ((m_tickSurplusMS += diffTime) >= 1000) {
		m_tickSurplusMS = 0;
		Tick();
	}
	if ((m_gcSurplusMS += diffTime) >= 30000) {
		m_gcSurplusMS = 0;
		lua_gc(L, LUA_GCCOLLECT, 0);
	}

	ApplyAllAoiTileActorOrders();
	UpdateAllFrameEvents();
	UpdateObjectsValue();
	FlushPlayersPacket();
	CleanMapHookInfos();
}

void MapInstance::Tick()
{
	sRPCManager.TickObjs();
	sAoiHandler.CollateAllActorMarker();
}

void MapInstance::UpdateObjectsValue()
{
	for (auto& pair : m_AutoPlayerStorageMap) {
		pair.second->SendValueUpdate();
	}
	for (auto& pair : m_PlayerStorageMap) {
		pair.second->SendValueUpdate();
	}
	for (auto& pair : m_CreatureStorageMap) {
		pair.second->SendValueUpdate();
	}
	for (auto& pair : m_StaticObjectStorageMap) {
		pair.second->SendValueUpdate();
	}
	for (auto& pair : m_AuraObjectStorageMap) {
		pair.second->SendValueUpdate();
	}
}

void MapInstance::FlushPlayersPacket()
{
	for (auto& pair : m_PlayerStorageMap) {
		pair.second->FlushPacket();
	}
}

bool MapInstance::IsInited() const
{
	if (GetTaskWaitInitCnt() > 0) {
		return false;
	}
	if (!m_gameMap.IsInited()) {
		return false;
	}
	return true;
}

bool MapInstance::IsCleaned() const
{
	if (!m_pendingEnterPlayers.empty()) {
		return false;
	}
	if (!m_StrayPlayerStorageMap.empty()) {
		return false;
	}
	if (!m_PlayerStorageMap.empty()) {
		return false;
	}
	for (auto& pLObj : m_pendingDeleteObjects) {
		if (pLObj->IsType(TYPE_PLAYER)) {
			return false;
		}
	}
	return true;
}

bool MapInstance::IsDeletable() const
{
	if (m_deleteExpireTime < GET_SYS_TIME) {
		return true;
	}
	return false;
}

void MapInstance::SetHook(IMapHook* pHook)
{
	DBGASSERT(m_pHook == NULL && pHook != NULL);
	m_pHook = pHook;
}

void MapInstance::ChangeFightStage(ArenaFightStage fightStage)
{
	m_fightStage = fightStage;
}

// flags = PSF_INCLUDE_PLAYER
bool MapInstance::ForeachAllPlayer(const LuaFunc& func, uint32 flags) const
{
	if ((flags & PSF_EXCLUDE_PLAYER) == 0) {
		for (const auto& pair : m_PlayerStorageMap) {
			if (func.Call<bool>(pair.first, pair.second)) {
				return true;
			}
		}
	}
	if ((flags & PSF_INCLUDE_STRAY_PLAYER) != 0) {
		for (const auto& pair : m_StrayPlayerStorageMap) {
			if (func.Call<bool>(pair.first, pair.second)) {
				return true;
			}
		}
	}
	if ((flags & PSF_INCLUDE_AUTO_PLAYER) != 0) {
		for (const auto& pair : m_AutoPlayerStorageMap) {
			if (func.Call<bool>(pair.first, pair.second)) {
				return true;
			}
		}
	}
	return false;
}

void MapInstance::BroadcastPacket2AllPlayer(uint32 opcode, const std::string_view& data) const
{
	for (const auto& pair : m_PlayerStorageMap) {
		pair.second->SendPacket(opcode, data);
	}
}

void MapInstance::BroadcastPacket2AllPlayer(const INetPacket& pck) const
{
	for (const auto& pair : m_PlayerStorageMap) {
		pair.second->SendPacket(pck);
	}
}

void MapInstance::SendSysMsgToAll(ChannelType channelType, uint32 msgFlags,
	uint32 msgId, const std::string_view& msgArgs) const
{
	NetPacket pack(SMSG_SYSTEM_MESSAGE);
	pack << (s8)channelType << msgFlags << msgId;
	pack.Append(msgArgs.data(), msgArgs.size());
	BroadcastPacket2AllPlayer(pack);
}

bool MapInstance::TryPlayerEnterMap(ObjGUID playerGuid,
	const PlayerTeleportInfo& tpInfo, INetStream& pck, int32 err)
{
	if (err != RPCErrorNone) {
		WLOG("Load player(%hu,%u) instance failed, %d.",
			playerGuid.SID, playerGuid.UID, err);
		return false;
	}

	inst_player_char ipcInfo;
	LoadFromINetStream(ipcInfo, pck);
	if (ipcInfo.ipcInstID != playerGuid.UID) {
		WLOG("Load player(%hu,%u) instance corruption.",
			playerGuid.SID, playerGuid.UID);
		return false;
	}

	auto pGSSession = &GSSession(playerGuid.SID);
	auto pSession = sGateServerMgr.GetGateServer(
		pGSSession->serverId(), tpInfo.gateSN, pGSSession->sn());
	if (pSession == NULL) {
		WLOG("New player(%hu,%u) instance failed, gate server disconnect.",
			playerGuid.SID, playerGuid.UID);
		return false;
	}

	auto pPlayer = new Player(TYPE_PLAYER, tpInfo.clientSN, pSession, pGSSession);
	pPlayer->InitPlayer(playerGuid, ipcInfo, tpInfo);

	if (!pPlayer->AddToWorld(this)) {
		WLOG("Add player(%hu,%u) instance to world[%hu,%hu,%u] failed.",
			playerGuid.SID, playerGuid.UID,
			m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID);
		delete pPlayer;
		return false;
	}

	NLOG("Map[%hu,%hu,%u], Player:%s(%hu,%u) Login! (MapInstance:%u,Total:%u)",
		m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID,
		pPlayer->GetName().c_str(), playerGuid.SID, playerGuid.UID,
		(u32)GetPlayerCount(), (u32)sPlayerMgr.GetPlayerCount());
	return true;
}

void MapInstance::SendPlayerEnterMapResp(bool isSucc, ObjGUID playerGuid)
{
	NetPacket resp(MS_CHARACTER_ENTER_MAP_RESP);
	resp << isSucc << GetGuid4Gs(playerGuid);
	GSSession(playerGuid.SID).PushSendPacket(resp);
}

void MapInstance::ForcePlayerLeave(Player* pPlayer)
{
	if (pPlayer->IsType(TYPE_PLAYER)) {
		if (pPlayer->getLastBackInstGuid() != InstGUID_NULL) {
			pPlayer->TeleportTo(
				pPlayer->getLastBackInstGuid(), pPlayer->getLastBackPos());
		} else {
			const MapInfo* pMapInfo = getMapInfo();
			if (pMapInfo->pop_map_id != pMapInfo->Id) {
				pPlayer->TeleportTo(
					MakeInstGuid((u16)MapInfo::Type::WorldMap, pMapInfo->pop_map_id),
					{pMapInfo->pop_pos_x, pMapInfo->pop_pos_y, pMapInfo->pop_pos_z,
					 pMapInfo->pop_o});
			} else {
				ObjGUID playerGuid = pPlayer->GetGuid();
				ELOG("Force Player(%hu,%u) Leave MapInstace[%hu,%hu,%hu] Error.",
					playerGuid.SID, playerGuid.UID,
					m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID);
			}
		}
	} else {
		pPlayer->RemoveFromWorld(this);
	}
}

void MapInstance::LogoutPlayer(ObjGUID playerGuid)
{
	NLOG("LogoutPlayer: Map[%hu,%hu,%u] Player(%hu,%u).",
		m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID,
		playerGuid.SID, playerGuid.UID);

	bool isRespDone = false;
	if (RemovePendingEnterPlayer(playerGuid, true)) {
		SendPlayerLeaveMapResp(true, playerGuid);
		isRespDone = true;
	}

	auto pPlayer = GetStrayPlayer(playerGuid);
	if (pPlayer != NULL && RemoveStrayPlayer(pPlayer, true)) {
		OnPlayerLeaveMap(pPlayer);
		SendPlayerLeaveMapResp(true, playerGuid);
		isRespDone = true;
	}

	TryPlayerLeaveMap(isRespDone, playerGuid);
}

void MapInstance::TryPlayerLeaveMap(bool isRespDone,
	ObjGUID playerGuid, InstGUID instGuid, const vector3f1f& tgtPos)
{
	NLOG("TryPlayerLeaveMap: Map[%hu,%hu,%u] Player(%hu,%u).",
		m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID,
		playerGuid.SID, playerGuid.UID);

	Player* pPlayer = GetPlayer(playerGuid);
	if (pPlayer != NULL) {
		pPlayer->RemoveFromWorld(this);
		SendPlayerLeaveMapResp(true, playerGuid, instGuid, tgtPos);
		isRespDone = true;
	}

	if (!isRespDone) {
		SendPlayerLeaveMapResp(false, playerGuid, instGuid, tgtPos);
		WLOG("[Teleport] Map[%hu,%hu,%u] Player(%hu,%u) Not In World.",
			m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID,
			playerGuid.SID, playerGuid.UID);
	}

	MapHookEvent_OnPlayerLeaveMap(playerGuid);
}

void MapInstance::OnPlayerLeaveMap(Player* pPlayer)
{
	pPlayer->Delete(this);
}

void MapInstance::SendPlayerLeaveMapResp(bool isSucc,
	ObjGUID playerGuid, InstGUID instGuid, const vector3f1f& tgtPos)
{
	NetPacket resp(MS_CHARACTER_LEAVE_MAP_RESP);
	resp << isSucc << GetGuid4Gs(playerGuid) << instGuid << tgtPos;
	GSSession(playerGuid.SID).PushSendPacket(resp);
}

void MapInstance::TeleportPlayer(Player* pPlayer, InstGUID instGuid,
	const vector3f1f& tgtPos, TeleportType tpType, uint32 tpFlags,
	const std::string_view& tpArgs)
{
	NetPacket tpPacket(MS_SWITCH_MAP);
	tpPacket << pPlayer->GetGuid4Gs()
		<< instGuid << tgtPos << (s32)tpType << tpFlags << tpArgs;
	pPlayer->SendPacket2Gs(tpPacket);
}

void MapInstance::EventOfflinePlayer(size_t gsIdx)
{
	for (auto itr = m_pendingEnterPlayers.begin();
		itr != m_pendingEnterPlayers.end();) {
		auto playerGuid = *itr++;
		if (gsIdx == -1 || gsIdx == playerGuid.SID) {
			LogoutPlayer(playerGuid);
		}
	}
	for (auto itr = m_StrayPlayerStorageMap.begin();
		itr != m_StrayPlayerStorageMap.end();) {
		auto playerGuid = itr++->first;
		if (gsIdx == -1 || gsIdx == playerGuid.SID) {
			LogoutPlayer(playerGuid);
		}
	}
	for (auto itr = m_PlayerStorageMap.begin();
		itr != m_PlayerStorageMap.end();) {
		auto playerGuid = itr++->first;
		if (gsIdx == -1 || gsIdx == playerGuid.SID) {
			LogoutPlayer(playerGuid);
		}
	}
}

void MapInstance::EventOfflineGatePlayer(void* pGateSession)
{
	for (auto itr = m_StrayPlayerStorageMap.begin();
		itr != m_StrayPlayerStorageMap.end();) {
		auto pPlayer = itr++->second;
		if (pPlayer->GetGateSession() == pGateSession) {
			pPlayer->Kick(LOGIN_ERROR_GATE_SERVER_OFFLINE);
		}
	}
	for (auto itr = m_PlayerStorageMap.begin();
		itr != m_PlayerStorageMap.end();) {
		auto pPlayer = itr++->second;
		if (pPlayer->GetGateSession() == pGateSession) {
			pPlayer->Kick(LOGIN_ERROR_GATE_SERVER_OFFLINE);
		}
	}
}

void MapInstance::EventAddPendingEnterPlayer(
	uint32 gsIdx, const std::shared_ptr<CharTeleportInfo>& tpInfoPtr)
{
	const CharTeleportInfo& tpInfo = *tpInfoPtr;
	auto playerGuid = GetGuidFromValue(gsIdx, tpInfo.playerGuid);

	if (IsPlayGameOver() || IstobeShutdown()) {
		return SendPlayerTeleportBeginEnterInstanceResult(
			gsIdx, InstanceIsClosed, tpInfo.playerGuid);
	}

	auto tgtPos = m_pHook->GetEntryPoint(gsIdx, tpInfo);
	if (tgtPos.x > INT_MAX) {
		return SendPlayerTeleportBeginEnterInstanceResult(
			gsIdx, InstanceForbid, tpInfo.playerGuid);
	}

	AddPendingEnterPlayer(playerGuid);
	MapHookEvent_OnPlayerPendingEnter(playerGuid);
	SendPlayerTeleportBeginEnterInstanceResult(
		gsIdx, CommonSuccess, tpInfo.playerGuid, tgtPos);
}

void MapInstance::EventPushStrayPlayer(
	Player* pPlayer, const vector3f1f& tgtPos, uint32 tpFlags)
{
	pPlayer->SetPosition({tgtPos.x, tgtPos.y, tgtPos.z});
	pPlayer->SetOrientation(tgtPos.o);

	pPlayer->ApplyTeleportFlagsOutOfWorld(tpFlags);

	auto playerGuid = pPlayer->GetGuid();
	if (!pPlayer->PushToWorld(this)) {
		WLOG("push stray player(%hu,%u) to world[%hu,%hu,%u] failed.",
			playerGuid.SID, playerGuid.UID,
			m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID);
		OnPlayerLeaveMap(pPlayer);
		return;
	}

	pPlayer->ApplyTeleportFlagsInWorld(tpFlags);

	NetPacket packFinish(SMSG_LOC_TELEPORT_FINISH);
	pPlayer->SendPacket(packFinish);
}

bool MapInstance::PushObject(LocatableObject* pLObj)
{
	DBGASSERT(pLObj != NULL && !pLObj->IsInWorld());
	if (pLObj == NULL || pLObj->IsInWorld()) {
		return false;
	}

	Player* pPlayer = NULL;
	ObjGUID playerGuid = ObjGUID_NULL;
	if (pLObj->IsType(TYPE_PLAYER)) {
		pPlayer = static_cast<Player*>(pLObj);
		playerGuid = pPlayer->GetGuid();
	}

	if (pPlayer != NULL) {
		switch (pPlayer->GetMapState()) {
		case PlayerEnterMapState:
			if (!RemovePendingEnterPlayer(pPlayer->GetGuid(), false)) {
				WLOG("Map[%hu,%hu,%u]: No pending player(%hu,%u) ...",
					m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID,
					playerGuid.SID, playerGuid.UID);
				return false;
			}
			break;
		case PlayerStrayMapState:
			if (!RemoveStrayPlayer(pPlayer, false)) {
				WLOG("Map[%hu,%hu,%u]: No stray player(%hu,%u) ...",
					m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID,
					playerGuid.SID, playerGuid.UID);
				return false;
			}
			break;
		default:
			WLOG("Map[%hu,%hu,%u]: Push player(%hu,%u) abnormal ...",
				m_instGuid.TID, m_instGuid.MAPID, m_instGuid.UID,
				playerGuid.SID, playerGuid.UID);
			return false;
		}
	}

	if (!IsValidPosition(pLObj->GetPosition())) {
		pLObj->SetPosition(GetNearestValidPosition(pLObj->GetPosition()));
	}

	if (pPlayer != NULL) {
		MaxNetPacket pck(SMSG_CREATE_OBJECT);
		pPlayer->BuildCreatePacketForPlayer(pck, pPlayer);
		pPlayer->SendPacket(pck);
	}

	const vector3f& pos = pLObj->GetPosition();
	PushAoiActorOrder(pLObj, pos.x, pos.z);
	PushTileActorOrder(pLObj, pos.x, pos.z);

	bool isOK = false;
	switch (pLObj->GetObjectType()) {
	case TYPE_AUTOPLAYER:
		isOK = m_AutoPlayerStorageMap.emplace(
			pLObj->GetGuid(), static_cast<AutoPlayer*>(pLObj)).second;
		break;
	case TYPE_PLAYER:
		isOK = m_PlayerStorageMap.emplace(
			pLObj->GetGuid(), static_cast<Player*>(pLObj)).second;
		break;
	case TYPE_CREATURE:
		isOK = m_CreatureStorageMap.emplace(
			pLObj->GetGuid(), static_cast<Creature*>(pLObj)).second;
		break;
	case TYPE_STATICOBJECT:
		isOK = m_StaticObjectStorageMap.emplace(
			pLObj->GetGuid(), static_cast<StaticObject*>(pLObj)).second;
		break;
	case TYPE_AURAOBJECT:
		isOK = m_AuraObjectStorageMap.emplace(
			pLObj->GetGuid(), static_cast<AuraObject*>(pLObj)).second;
		break;
	default:
		assert(false && "can't reach here.");
		break;
	}
	if (!isOK) {
		ELOG("Repeat push object[%hu,%hu,%u]!!!",
			pLObj->GetGuid().SID, pLObj->GetGuid().TID, pLObj->GetGuid().UID);
	}

	if (pLObj->IsKindOf(TYPE_PLAYER)) {
		MapHookEvent_OnPlayerEnter(static_cast<Player*>(pLObj));
	}

	return true;
}

bool MapInstance::RemoveObject(LocatableObject* pLObj)
{
	DBGASSERT(pLObj != NULL && pLObj->IsInWorld());
	if (pLObj == NULL || !pLObj->IsInWorld()) {
		return false;
	}

	PopupAoiActorOrder(pLObj, false);
	PopupTileActorOrder(pLObj);

	switch (pLObj->GetObjectType()) {
	case TYPE_AUTOPLAYER:
		m_AutoPlayerStorageMap.erase(pLObj->GetGuid());
		break;
	case TYPE_PLAYER:
		m_PlayerStorageMap.erase(pLObj->GetGuid());
		break;
	case TYPE_CREATURE:
		m_CreatureStorageMap.erase(pLObj->GetGuid());
		break;
	case TYPE_STATICOBJECT:
		m_StaticObjectStorageMap.erase(pLObj->GetGuid());
		break;
	case TYPE_AURAOBJECT:
		m_AuraObjectStorageMap.erase(pLObj->GetGuid());
		break;
	default:
		assert(false && "can't reach here.");
		break;
	}

	if (pLObj->IsKindOf(TYPE_PLAYER)) {
		MapHookEvent_OnPlayerLeave(static_cast<Player*>(pLObj));
	}

	return true;
}

bool MapInstance::AddPendingEnterPlayer(ObjGUID guid)
{
	return m_pendingEnterPlayers.insert(guid).second;
}

bool MapInstance::RemovePendingEnterPlayer(ObjGUID guid, bool isLeave)
{
	return m_pendingEnterPlayers.erase(guid) != 0;
}

bool MapInstance::AddStrayPlayer(Player* pPlayer)
{
	return m_StrayPlayerStorageMap.emplace(pPlayer->GetGuid(), pPlayer).second;
}

bool MapInstance::RemoveStrayPlayer(Player* pPlayer, bool isLeave)
{
	return m_StrayPlayerStorageMap.erase(pPlayer->GetGuid()) != 0;
}

void MapInstance::AddPendingDeleteObject(LocatableObject* pLObj)
{
	m_pendingDeleteObjects.insert(pLObj);
}

void MapInstance::SendPlayerTeleportBeginEnterInstanceResult(uint32 gsIdx,
	GErrorCode errCode, uint64 playerGuid, const vector3f1f& tgtPos)
{
	NetPacket resp(MS_CHARACTER_TELEPORT_BEGIN_ENTER_INSTANCE_RESULT);
	resp << errCode << playerGuid;
	if (errCode == CommonSuccess) {
		resp << m_ownerGuid << m_instGuid << tgtPos;
	}
	GSSession(gsIdx).PushSendPacket(resp);
}

Creature* MapInstance::DeployCreature(
	uint32 entry, float x, float y, float z, float o,
	uint32 level, uint32 idleType, uint32 lifeTime)
{
	CreatureSpawnArgs args;
	args.lifeTime = lifeTime;
	args.idleType = idleType;
	args.level = level;
	return CreateCustomCreature(entry, {x, y, z, o}, &args);
}

StaticObject* MapInstance::DeployStaticObject(
	uint32 entry, float x, float y, float z, float o,
	float radius, uint32 lifeTime)
{
	SObjSpawnArgs args;
	args.lifeTime = lifeTime;
	args.radius = radius;
	return CreateCustomStaticObject(entry, {x, y, z, o}, &args);
}

void MapInstance::DeployStageCreature(LuaFunc func, uint32 entry, size_t n,
	float x, float y, float z, float o, float range,
	uint32 level, uint32 idleType, uint32 lifeTime)
{
	for (size_t i = 0; i < n; ++i) {
		CreatureSpawnArgs args;
		args.lifeTime = lifeTime;
		args.idleType = idleType;
		args.level = level;
		vector3f1f pos(x, y, z, o);
		if (range != .0f) {
			pos.xyz(RandomPositionByRange({x,y,z}, range));
			pos.o = System::Rand(-PI, PI);
		}
		Creature* pCreature = CreateCustomCreature(entry, pos, &args);
		if (pCreature != NULL && func.is_alive()) {
			func.Call<void>(pCreature, i);
		}
	}
}

void MapInstance::DeployStageStaticObject(LuaFunc func, uint32 entry, size_t n,
	float x, float y, float z, float o, float range,
	float radius, uint32 lifeTime)
{
	for (size_t i = 0; i < n; ++i) {
		SObjSpawnArgs args;
		args.lifeTime = lifeTime;
		args.radius = radius;
		vector3f1f pos(x, y, z, o);
		if (range != .0f) {
			pos.xyz(RandomPositionByRange({x,y,z}, range));
			pos.o = System::Rand(-PI, PI);
		}
		StaticObject* pSObj = CreateCustomStaticObject(entry, pos, &args);
		if (pSObj != NULL && func.is_alive()) {
			func.Call<void>(pSObj, i);
		}
	}
}

void MapInstance::CleanupStageCreature(uint32 entry)
{
	for (const auto& pair : m_CreatureStorageMap) {
		auto pCreature = pair.second;
		if (pCreature->GetEntry() == entry) {
			pCreature->FastDisappear();
		}
	}
}

void MapInstance::CleanupStageStaticObject(uint32 entry)
{
	for (const auto& pair : m_StaticObjectStorageMap) {
		auto pSObj = pair.second;
		if (pSObj->GetEntry() == entry) {
			pSObj->FastDisappear();
		}
	}
}

void MapInstance::ClearAllHostileForcesCreature()
{
	for (const auto& pair : m_CreatureStorageMap) {
		auto pCreature = pair.second;
		if (pCreature->GetProto()->charRace == (u32)CharRaceType::HostileForces) {
			pCreature->FastDisappear();
		}
	}
}

Creature* MapInstance::SpawnCreature(const CreatureSpawn* pSpawn)
{
	auto pCreature = new Creature(TYPE_CREATURE);
	pCreature->SetGuid(
		GetGuidFromLowGuid(TYPE_CREATURE, ++m_creatureSeed));

	if (!pCreature->InitCreature(pSpawn)) {
		delete pCreature;
		return NULL;
	}

	if (!pCreature->PushToWorld(this)) {
		delete pCreature;
		return NULL;
	}

	MapHookEvent_OnCreatureSpawn(pCreature);

	return pCreature;
}

Creature* MapInstance::CreateCustomCreature(
	uint32 entry, const vector3f1f& pos, const CreatureSpawnArgs* args)
{
	auto pSpawn = std::make_shared<CreatureSpawn>();
	auto& spawn = *pSpawn;

	spawn.entry = entry;
	spawn.map_id = m_instGuid.MAPID;
	spawn.x = pos.x;
	spawn.y = pos.y;
	spawn.z = pos.z;
	spawn.o = pos.o;

	if (args != NULL) {
		if (args->isRespawn && args->lifeTime == 0) {
			spawn.flags.isRespawn = true;
		}
		if (args->spawnId != 0) {
			spawn.spawnId = args->spawnId;
		}
		if (args->idleType != 0) {
			spawn.idleType = args->idleType;
		}
		if (args->level != 0) {
			spawn.level = args->level;
		}
		if (args->round != 0) {
			spawn.round = args->round;
		}
	}

	auto pCreature = SpawnCreature(&spawn);
	if (pCreature == NULL) {
		return NULL;
	}

	if (args != NULL) {
		if (args->lifeTime != 0) {
			pCreature->CreateTimerX(
				std::bind(&LocatableObject::FastDisappear, pCreature),
				args->lifeTime * 1000, 1);
		}
	}

	pCreature->SetSpawnPrivate(pSpawn);

	return pCreature;
}

StaticObject* MapInstance::SpawnStaticObject(const StaticObjectSpawn* pSpawn)
{
	auto pStaticObject = new StaticObject();
	pStaticObject->SetGuid(
		GetGuidFromLowGuid(TYPE_STATICOBJECT, ++m_staticObjectSeed));

	if (!pStaticObject->InitStaticObject(pSpawn)) {
		delete pStaticObject;
		return NULL;
	}

	if (!pStaticObject->PushToWorld(this)) {
		delete pStaticObject;
		return NULL;
	}

	MapHookEvent_OnStaticObjectSpawn(pStaticObject);

	return pStaticObject;
}

StaticObject* MapInstance::CreateCustomStaticObject(
	uint32 entry, const vector3f1f& pos, const SObjSpawnArgs* args)
{
	auto pSpawn = std::make_shared<StaticObjectSpawn>();
	auto& spawn = *pSpawn;

	spawn.entry = entry;
	spawn.map_id = m_instGuid.MAPID;
	spawn.x = pos.x;
	spawn.y = pos.y;
	spawn.z = pos.z;
	spawn.o = pos.o;

	if (args != NULL) {
		if (args->isRespawn && args->lifeTime == 0) {
			spawn.flags.isRespawn = true;
		}
		if (args->spawnId != 0) {
			spawn.spawnId = args->spawnId;
		}
		if (args->radius != .0f) {
			spawn.radius = args->radius;
		}
	}

	auto pStaticObject = SpawnStaticObject(&spawn);
	if (pStaticObject == NULL) {
		return NULL;
	}

	if (args != NULL) {
		if (args->lifeTime != 0) {
			pStaticObject->CreateTimerX(
				std::bind(&LocatableObject::FastDisappear, pStaticObject),
				args->lifeTime * 1000, 1);
		}
	}

	pStaticObject->SetSpawnPrivate(pSpawn);

	return pStaticObject;
}

AuraObject* MapInstance::SpawnAuraObject(const AuraPrototype* pProto,
	Spell* pSpell, uint32 effectIndex, Unit* pOwner)
{
	auto pAuraObject = new AuraObject();
	pAuraObject->SetGuid(
		GetGuidFromLowGuid(TYPE_AURAOBJECT, ++m_auraObjectSeed));

	if (!pAuraObject->InitAuraObject(pProto, pSpell, effectIndex, pOwner)) {
		delete pAuraObject;
		return NULL;
	}

	if (!pAuraObject->PushToWorld(this)) {
		delete pAuraObject;
		return NULL;
	}

	pSpell->GetCaster()->AddReferAuraObject(pAuraObject);
	pOwner->AddReferAuraObject(pAuraObject);

	return pAuraObject;
}

void MapInstance::LoadAllSpawnObjects()
{
	for (auto& pSpawn : m_gameMap.GetCreatureSpawns(getMapType())) {
		auto pSpawnObject = new CreatureSpawnObject(this, pSpawn);
		pSpawnObject->OnPush(&sAoiHandler, pSpawn->x, pSpawn->z);
		m_allSpawnObject.insert(pSpawnObject);
	}
	for (auto& pSpawn : m_gameMap.GetStaticObjectSpawns(getMapType())) {
		auto pSpawnObject = new StaticObjectSpawnObject(this, pSpawn);
		pSpawnObject->OnPush(&sAoiHandler, pSpawn->x, pSpawn->z);
		m_allSpawnObject.insert(pSpawnObject);
	}
}

void MapInstance::RemoveSpawnObject(AoiActor* actor)
{
	m_allSpawnObject.erase(actor);
	actor->OnPopup();
	delete actor;
}

void MapInstance::RemoveTeamMember(Player* pPlayer)
{
	MapTeam* pTeam = pPlayer->GetTeam();
	if (pTeam != NULL) {
		pPlayer->SetTeam(NULL);
		if (pTeam->GetInMapMemberCount() == 0) {
			m_MapTeamManager.DeleteTeam(pTeam->GetGsId(), pTeam->GetId());
		}
	}
}

void MapInstance::ApplyNavPhysicsFlags(uint16 flags, bool isEnable)
{
	if (isEnable) {
		m_navPhysicsFlags |= flags;
	} else {
		m_navPhysicsFlags &= ~flags;
	}
	NetPacket pack(SMSG_UPDATE_NAV_PHYSICS_FLAGS);
	pack << m_navPhysicsFlags;
	BroadcastPacket2AllPlayer(pack);
}

size_t MapInstance::GetMostPossiblePlayerCount() const
{
	return GetPendingEnterPlayerCount() + GetStrayPlayerCount() + GetPlayerCount();
}

bool MapInstance::IsBackMapInstance() const
{
	const MapInfo* pMapInfo = getMapInfo();
	switch (MapInfo::Type(getMapType())) {
	case MapInfo::Type::WorldMap:
		return true;
	default:
		return false;
	}
}

bool MapInstance::IsSavePlayerPosition() const
{
	const MapInfo* pMapInfo = getMapInfo();
	switch (MapInfo::Type(getMapType())) {
	case MapInfo::Type::WorldMap:
		return true;
	default:
		return false;
	}
}

void MapInstance::ChangeObjectLocation(LocatableObject *pLObj)
{
	const auto& pos = pLObj->GetPosition();
	MoveAoiActorOrder(pLObj, pos.x, pos.z);
	MoveTileActorOrder(pLObj, pos.x, pos.z);
}

bool MapInstance::IsValidPosition(const vector3f& pos) const
{
	const TileDefine& tileDefine = m_gameMap.GetTileDefine();
	return tileDefine.IsValidTileByCoords(pos.x, pos.z);
}

vector3f MapInstance::GetNearestValidPosition(const vector3f& pos) const
{
	vector3f newpos = pos;
	const TileDefine& tileDefine = m_gameMap.GetTileDefine();
	tileDefine.GetNearestValidCoords(newpos.x, newpos.z);
	return newpos;
}

void MapInstance::ApplyAoiTileActorOrder(LocatableObject *pLObj)
{
	if (pLObj != NULL) {
		ApplyAoiActorOrder(pLObj);
		ApplyTileActorOrder(pLObj);
	}
}

void MapInstance::ApplyAllAoiTileActorOrders()
{
	ApplyAllAoiActorOrders();
	ApplyAllTileActorOrders();
}

void MapInstance::ReloadAoiActorRadiusOrder(AoiActor* pActor)
{
	auto rst = m_allAoiActorOrders.emplace(pActor, defAoiOrderArgs);
	auto& args = rst.first->second;
	args.flags |= (int)AoiOrderFlag::ReloadRadius;
}

void MapInstance::ReloadAoiActorObserverOrder(AoiActor* pActor)
{
	auto rst = m_allAoiActorOrders.emplace(pActor, defAoiOrderArgs);
	auto& args = rst.first->second;
	args.flags |= (int)AoiOrderFlag::ReloadObserver;
}

void MapInstance::ReloadAoiActorSubjectOrder(AoiActor* pActor)
{
	auto rst = m_allAoiActorOrders.emplace(pActor, defAoiOrderArgs);
	auto& args = rst.first->second;
	args.flags |= (int)AoiOrderFlag::ReloadSubject;
}

void MapInstance::ReloadAoiActorStatusOrder(AoiActor* pActor)
{
	auto rst = m_allAoiActorOrders.emplace(pActor, defAoiOrderArgs);
	auto& args = rst.first->second;
	args.flags |= (int)AoiOrderFlag::ReloadStatus;
}

void MapInstance::PushAoiActorOrder(AoiActor* pActor, float x, float z)
{
	auto rst = m_allAoiActorOrders.emplace(pActor, defAoiOrderArgs);
	auto& args = rst.first->second;
	if (args.order == AoiOrder::None) {
		args.order = AoiOrder::Push;
	} else {
		switch (args.order) {
		case AoiOrder::Popup:
			args.order = AoiOrder::Move;
			break;
		default:
			assert(false && "can't reach here.");
			break;
		}
	}
	args.x = x, args.z = z;
}

void MapInstance::MoveAoiActorOrder(AoiActor* pActor, float x, float z)
{
	auto rst = m_allAoiActorOrders.emplace(pActor, defAoiOrderArgs);
	auto& args = rst.first->second;
	if (args.order == AoiOrder::None) {
		args.order = AoiOrder::Move;
	} else {
		switch (args.order) {
		case AoiOrder::Push:
		case AoiOrder::Move:
			break;
		default:
			assert(false && "can't reach here.");
			break;
		}
	}
	args.x = x, args.z = z;
}

void MapInstance::PopupAoiActorOrder(AoiActor* pActor, bool deletable)
{
	auto rst = m_allAoiActorOrders.emplace(pActor, defAoiOrderArgs);
	auto& args = rst.first->second;
	if (args.order == AoiOrder::None) {
		args.order = AoiOrder::Popup;
	} else {
		switch (args.order) {
		case AoiOrder::Push:
			m_allAoiActorOrders.erase(rst.first);
			break;
		case AoiOrder::Move:
			args.order = AoiOrder::Popup;
			args.deletable = deletable;
			break;
		default:
			assert(false && "can't reach here.");
			break;
		}
	}
}

void MapInstance::ApplyAoiActorOrder(AoiActor* pActor)
{
	auto itr = m_allAoiActorOrders.find(pActor);
	if (itr != m_allAoiActorOrders.end()) {
		auto[pActor, args] = *itr;
		m_allAoiActorOrders.erase(itr);
		ApplyAoiActorOrder(pActor, args);
	}
}

void MapInstance::ApplyAllAoiActorOrders()
{
	while (!m_allAoiActorOrders.empty()) {
		auto itr = m_allAoiActorOrders.begin();
		auto[pActor, args] = *itr;
		m_allAoiActorOrders.erase(itr);
		ApplyAoiActorOrder(pActor, args);
	}
}

void MapInstance::ApplyAoiActorOrder(AoiActor* pActor, const AoiOrderArgs& args)
{
	switch (args.order) {
	case AoiOrder::Push:
		pActor->OnPush(&sAoiHandler, args.x, args.z);
		break;
	case AoiOrder::Move:
		pActor->OnMove(args.x, args.z);
		break;
	case AoiOrder::Popup:
		pActor->OnPopup();
		if (args.deletable) {
			delete pActor;
		}
		break;
	}
	switch (args.order) {
	case AoiOrder::None:
	case AoiOrder::Move:
		if ((args.flags & (int)AoiOrderFlag::ReloadRadius) != 0) {
			sAoiHandler.ReloadActorRadius(pActor);
		}
		if ((args.flags & (int)AoiOrderFlag::ReloadObserver) != 0) {
			sAoiHandler.ReloadActorObserver(pActor);
		}
		if ((args.flags & (int)AoiOrderFlag::ReloadSubject) != 0) {
			sAoiHandler.ReloadActorSubject(pActor);
		}
	}
}

void MapInstance::PushTileActorOrder(TileActor* pActor, float x, float z)
{
	auto rst = m_allTileActorOrders.emplace(pActor, defTileOrderArgs);
	auto& args = rst.first->second;
	if (rst.second) {
		args.order = TileOrder::Push;
	} else {
		switch (args.order) {
		case TileOrder::Popup:
			args.order = TileOrder::Move;
			break;
		default:
			assert(false && "can't reach here.");
			break;
		}
	}
	args.x = x, args.z = z;
}

void MapInstance::MoveTileActorOrder(TileActor* pActor, float x, float z)
{
	auto rst = m_allTileActorOrders.emplace(pActor, defTileOrderArgs);
	auto& args = rst.first->second;
	if (rst.second) {
		args.order = TileOrder::Move;
	} else {
		switch (args.order) {
		case TileOrder::Push:
		case TileOrder::Move:
			break;
		default:
			assert(false && "can't reach here.");
			break;
		}
	}
	args.x = x, args.z = z;
}

void MapInstance::PopupTileActorOrder(TileActor* pActor)
{
	auto rst = m_allTileActorOrders.emplace(pActor, defTileOrderArgs);
	auto& args = rst.first->second;
	if (rst.second) {
		args.order = TileOrder::Popup;
	} else {
		switch (args.order) {
		case TileOrder::Push:
			m_allTileActorOrders.erase(rst.first);
			break;
		case TileOrder::Move:
			args.order = TileOrder::Popup;
			break;
		default:
			assert(false && "can't reach here.");
			break;
		}
	}
}

void MapInstance::ApplyTileActorOrder(TileActor* pActor)
{
	auto itr = m_allTileActorOrders.find(pActor);
	if (itr != m_allTileActorOrders.end()) {
		auto[pActor, args] = *itr;
		m_allTileActorOrders.erase(itr);
		ApplyTileActorOrder(pActor, args);
	}
}

void MapInstance::ApplyAllTileActorOrders()
{
	while (!m_allTileActorOrders.empty()) {
		auto itr = m_allTileActorOrders.begin();
		auto[pActor, args] = *itr;
		m_allTileActorOrders.erase(itr);
		ApplyTileActorOrder(pActor, args);
	}
}

void MapInstance::ApplyTileActorOrder(TileActor* pActor, const TileOrderArgs& args)
{
	switch (args.order) {
	case TileOrder::Push:
		pActor->OnPush(&sTileHandler, args.x, args.z);
		break;
	case TileOrder::Move:
		pActor->OnMove(args.x, args.z);
		break;
	case TileOrder::Popup:
		pActor->OnPopup();
		break;
	}
}

Player* MapInstance::GetAvailablePlayer(ObjGUID guid) const
{
	auto itr = m_PlayerStorageMap.find(guid);
	if (itr != m_PlayerStorageMap.end()) {
		return itr->second;
	}
	itr = m_StrayPlayerStorageMap.find(guid);
	if (itr != m_StrayPlayerStorageMap.end()) {
		return itr->second;
	}
	return NULL;
}

AutoPlayer* MapInstance::GetAutoPlayer(ObjGUID guid) const
{
	auto itr = m_AutoPlayerStorageMap.find(guid);
	return itr != m_AutoPlayerStorageMap.end() ? itr->second : NULL;
}

Player* MapInstance::GetStrayPlayer(ObjGUID guid) const
{
	auto itr = m_StrayPlayerStorageMap.find(guid);
	return itr != m_StrayPlayerStorageMap.end() ? itr->second : NULL;
}

Player* MapInstance::GetPlayer(ObjGUID guid) const
{
	auto itr = m_PlayerStorageMap.find(guid);
	return itr != m_PlayerStorageMap.end() ? itr->second : NULL;
}

Creature* MapInstance::GetCreature(ObjGUID guid) const
{
	auto itr = m_CreatureStorageMap.find(guid);
	return itr != m_CreatureStorageMap.end() ? itr->second : NULL;
}

Creature* MapInstance::GetCreatureByEntry(uint32 entry) const
{
	for (const auto& pair : m_CreatureStorageMap) {
		auto pCreature = pair.second;
		if (pCreature->GetEntry() == entry) {
			return pCreature;
		}
	}
	return NULL;
}

StaticObject* MapInstance::GetStaticObject(ObjGUID guid) const
{
	auto itr = m_StaticObjectStorageMap.find(guid);
	return itr != m_StaticObjectStorageMap.end() ? itr->second : NULL;
}

StaticObject* MapInstance::GetStaticObjectByEntry(uint32 entry) const
{
	for (const auto& pair : m_StaticObjectStorageMap) {
		auto pSObj = pair.second;
		if (pSObj->GetEntry() == entry) {
			return pSObj;
		}
	}
	return NULL;
}

AuraObject* MapInstance::GetAuraObject(ObjGUID guid) const
{
	auto itr = m_AuraObjectStorageMap.find(guid);
	return itr != m_AuraObjectStorageMap.end() ? itr->second : NULL;
}

Unit* MapInstance::GetUnit(ObjGUID guid) const
{
	if (guid == ObjGUID_NULL) {
		return NULL;
	}
	if (IS_TYPE(guid.TID, TYPE_AUTOPLAYER)) {
		return GetAutoPlayer(guid);
	}
	if (IS_TYPE(guid.TID, TYPE_PLAYER)) {
		return GetPlayer(guid);
	}
	if (IS_KIND_OF(guid.TID, TYPE_CREATURE)) {
		return GetCreature(guid);
	}
	return NULL;
}

LocatableObject* MapInstance::GetLocatableObject(ObjGUID guid) const
{
	if (guid == ObjGUID_NULL) {
		return NULL;
	}
	if (IS_TYPE(guid.TID, TYPE_AUTOPLAYER)) {
		return GetAutoPlayer(guid);
	}
	if (IS_TYPE(guid.TID, TYPE_PLAYER)) {
		return GetPlayer(guid);
	}
	if (IS_KIND_OF(guid.TID, TYPE_CREATURE)) {
		return GetCreature(guid);
	}
	if (IS_KIND_OF(guid.TID, TYPE_STATICOBJECT)) {
		return GetStaticObject(guid);
	}
	if (IS_KIND_OF(guid.TID, TYPE_AURAOBJECT)) {
		return GetAuraObject(guid);
	}
	return NULL;
}

bool MapInstance::ForeachPlayer(const LuaFunc& func) const
{
	for (const auto& pair : m_PlayerStorageMap) {
		if (func.Call<bool>(pair.second)) {
			return true;
		}
	}
	return false;
}

bool MapInstance::ForeachCreature(const LuaFunc& func) const
{
	for (const auto& pair : m_CreatureStorageMap) {
		if (func.Call<bool>(pair.second)) {
			return true;
		}
	}
	return false;
}

bool MapInstance::ForeachStaticObject(const LuaFunc& func) const
{
	for (const auto& pair : m_StaticObjectStorageMap) {
		if (func.Call<bool>(pair.second)) {
			return true;
		}
	}
	return false;
}

bool MapInstance::ForeachAuraObject(const LuaFunc& func) const
{
	for (const auto& pair : m_AuraObjectStorageMap) {
		if (func.Call<bool>(pair.second)) {
			return true;
		}
	}
	return false;
}

void MapInstance::MapHookEvent_OnPlayerPendingEnter(ObjGUID playerGuid)
{
	ForeachHookInfo4Event(m_mapHookInfos, MapHookEvent::OnPlayerPendingEnter, "OnPlayerPendingEnter", playerGuid);
}

void MapInstance::MapHookEvent_OnPlayerLeaveMap(ObjGUID playerGuid)
{
	ForeachHookInfo4Event(m_mapHookInfos, MapHookEvent::OnPlayerLeaveMap, "OnPlayerLeaveMap", playerGuid);
}

void MapInstance::MapHookEvent_OnPlayerEnter(Player* pPlayer)
{
	ForeachHookInfo4Event(m_mapHookInfos, MapHookEvent::OnPlayerEnter, "OnPlayerEnter", pPlayer);
}

void MapInstance::MapHookEvent_OnPlayerLeave(Player* pPlayer)
{
	ForeachHookInfo4Event(m_mapHookInfos, MapHookEvent::OnPlayerLeave, "OnPlayerLeave", pPlayer);
}

void MapInstance::MapHookEvent_OnCreatureSpawn(Creature* pCreature)
{
	ForeachHookInfo4Event(m_mapHookInfos, MapHookEvent::OnCreatureSpawn, "OnCreatureSpawn", pCreature);
}

void MapInstance::MapHookEvent_OnStaticObjectSpawn(StaticObject* pSObj)
{
	ForeachHookInfo4Event(m_mapHookInfos, MapHookEvent::OnStaticObjectSpawn, "OnStaticObjectSpawn", pSObj);
}

void MapInstance::MapHookEvent_OnUnitHurted(Unit* pUnit, Unit* pHurter, uint64 hurtValue)
{
	ForeachHookInfo4Event(m_mapHookInfos, MapHookEvent::OnUnitHurted, "OnUnitHurted", pUnit, pHurter, hurtValue);
}

void MapInstance::MapHookEvent_OnUnitKilled(Unit* pUnit, Unit* pKiller)
{
	ForeachHookInfo4Event(m_mapHookInfos, MapHookEvent::OnUnitKilled, "OnUnitKilled", pUnit, pKiller);
}

void MapInstance::MapHookEvent_OnUnitDead(Unit* pUnit, Unit* pKiller)
{
	ForeachHookInfo4Event(m_mapHookInfos, MapHookEvent::OnUnitDead, "OnUnitDead", pUnit, pKiller);
}

void MapInstance::MapHookEvent_OnSendMessage(const char* funcName, const LuaTable& args)
{
	ForeachHookInfo4Event<MapHookInfo, MapHookEvent, const char*, const LuaTable&>(
		m_mapHookInfos, MapHookEvent::OnSendMessage, "OnSendMessage", funcName, args);
}

uint32 MapInstance::AttachMapHookInfo(LuaTable&& t)
{
	auto key = NewMapHookKey();
	auto pMapHookInfo = new MapHookInfo{true};
	HookEvents2bitset(t.get<LuaTable>("events"), pMapHookInfo->events);
	pMapHookInfo->isMember = t.get<bool>("isMember?");
	pMapHookInfo->t = LuaRef(t.getL(), t.index());
	m_mapHookInfos.emplace(key, pMapHookInfo);
	if (pMapHookInfo->events.test((int)MapHookEvent::OnSendMessage)) {
		AddMethod_OnSendMessage(pMapHookInfo, t);
	}
	return key;
}

void MapInstance::DetachMapHookInfo(uint32 key)
{
	auto itr = m_mapHookInfos.find(key);
	if (itr == m_mapHookInfos.end()) {
		WLOG("DetachMapHookInfo: Can't find key %d.", key);
		return;
	}
	auto pMapHookInfo = itr->second;
	if (!pMapHookInfo->isAvail) {
		WLOG("DetachMapHookInfo: key %d isn't available.", key);
		return;
	}
	if (pMapHookInfo->events.test(int(MapHookEvent::OnHookDetach))) {
		LuaFuncs(pMapHookInfo->t).CallStaticMethod<void>("Cleanup");
	}
	pMapHookInfo->isAvail = false;
	m_gcMapHookKeys.push_back(key);
}

void MapInstance::DestructAllMapHookInfos()
{
	for (auto& pair : m_mapHookInfos) {
		delete pair.second;
	}
}

void MapInstance::CleanMapHookInfos()
{
	for (; !m_gcMapHookKeys.empty(); m_gcMapHookKeys.pop_back()) {
		auto key = m_gcMapHookKeys.back();
		auto itr = m_mapHookInfos.find(key);
		if (itr == m_mapHookInfos.end()) {
			WLOG("CleanMapHookInfo: Can't find key %d.", key);
			continue;
		}
		auto pMapHookInfo = itr->second;
		if (pMapHookInfo->isAvail) {
			WLOG("CleanMapHookInfo: key %d is available.", key);
			continue;
		}
		m_mapHookInfos.erase(itr);
		delete pMapHookInfo;
	}
}
